<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>面试题目</title>
</head>
<body>
  <script>
    // 1. 写一个函数，去两个数组的交集
    function intersection(arr1, arr2) {
      if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
        return [];
      }
      if (arr1.length === 0 || arr2.length === 0) {
        return [];
      }
      let result = [];
      arr1.forEach((el) => {
        if (arr2.indexOf(el) !== -1) {
            result.push(el);
          }
      })
      return result;
    }

    // 写一函数，实现传参返回其累加的值
    // add(1)(2)(3) // 6
    // add(1, 2, 3) // 6
    // add(1)(2, 3) // 6
    // add(1, 2)(3) // 6
    function add(...args) {
      let sum = [...args]
      function inner(...args1) {
        if (args1.length === 0) {
          return sum.reduce(function(pre,cur){
            return pre+cur
          },0)
        }
        sum.push(...args1)
        return inner
      }
      // inner.toString = function () { // 重写toSting() 方法改写函数的默认返回值
      //   return sum.reduce(function(pre,cur){
      //     return pre+cur
      //   },0)
      // }
      return inner
    }
    const test = add(1, 2, 3)()
    console.log(add(1)(2)(3)(), test);
    console.log(test);

    // 手动实现数组flat
    function flat(arr) {
      let result = [];
      for (let i = 0; i < arr.length; i++) {
        if (Array.isArray(arr[i])) {
          result = result.concat(flat(arr[i]));
        } else {
          result.push(arr[i]);
        }
      }
      return result;
    }
    console.log(flat([1, 2, [3, 4, [5, 6]]]));

    // 1.说一下浏览器的强缓存和协商缓存，相关的控制字段有哪些
//     强缓存
// 强缓存是指浏览器在第一次请求资源时，服务器会将资源缓存到浏览器，并设置一个过期时间。在过期时间之内，浏览器再次请求相同的资源时，会直接从缓存中读取，而不会向服务器发送请求。这种方式可以减少网络流量，提高页面的加载速度。

// 强缓存的相关的控制字段主要有：

// Expires：HTTP响应头中的一个日期时间字段，表示资源的过期时间。浏览器会根据这个时间来判断资源是否过期。
// Cache-Control：这是一个更灵活的缓存控制字段，它可以指定缓存的最大年龄（max-age）、是否可以被代理服务器缓存（public/private）、是否可以被存储在本地（no-store）、是否可以被重新验证（must-revalidate）等。
// 协商缓存
// 协商缓存是指浏览器在再次请求资源时，会先向服务器发送一个请求，询问资源是否发生了变化。如果资源没有发生变化，服务器会返回一个304 Not Modified状态码，告诉浏览器可以使用本地缓存中的资源；如果资源发生了变化，服务器会返回新的资源以及200 OK状态码。

// 协商缓存的相关的控制字段主要有：

// Last-Modified：HTTP响应头中的一个日期时间字段，表示资源的最后修改时间。浏览器在再次请求资源时，会在请求头中带上If-Modified-Since字段，值为上次响应中的Last-Modified值，服务器根据这个值来判断资源是否发生了变化。
// ETag：HTTP响应头中的一个字符串字段，表示资源的唯一标识符。浏览器在再次请求资源时，会在请求头中带上If-None-Match字段，值为上次响应中的ETag值，服务器根据这个值来判断资源是否发生了变化。
// 协商缓存可以在不传输实际资源内容的情况下，验证资源的有效性，从而减少不必要的数据传输。


// 2.聊一聊CommonJS以及ES6的模块机制
// CommonJS 和 ES6 模块机制是 JavaScript 中的两种不同的模块化系统。

// CommonJS
// CommonJS 是一个旨在为浏览器之外的 JavaScript 环境提供模块化标准的提案。Node.js 就是使用 CommonJS 模块化系统的典型代表。CommonJS 的核心特性包括：

// 每个模块都是一个单独的文件。
// 模块通过 require 函数同步加载。
// 模块通过 exports 对象或 module.exports 变量导出接口。
// 模块加载是缓存的过程，即每个模块只会在第一次被加载时执行一次，之后的 require 调用会返回缓存的结果。
// CommonJS 示例：

// // moduleA.js
// module.exports = function( value ){
//     return value * 2;
// };

// // moduleB.js
// var multiplyByTwo = require('./moduleA');
// var result = multiplyByTwo(4);
// console.log(result); // 输出 8
// ES6 模块
// ES6（ECMAScript 2015）引入了官方的模块化系统，旨在为 JavaScript 提供标准化的模块定义和加载机制。ES6 模块的核心特性包括：

// 使用 import 关键字导入模块。
// 使用 export 关键字导出模块接口。
// 模块解析是异步的，默认情况下是静态的（编译时确定依赖关系）。
// 模块导出的是值的引用，而不是值的拷贝，这意味着模块内部的变化会反映到导入模块的地方。
// 有tree-shaking的优化可能性，因为模块是静态分析的。
// ES6 模块示例：

// // moduleA.js
// export function multiplyByTwo(value) {
//     return value * 2;
// }

// // moduleB.js
// import { multiplyByTwo } from './moduleA';
// const result = multiplyByTwo(4);
// console.log(result); // 输出 8
// 对比
// 加载机制：CommonJS 是同步加载，适用于服务器环境；ES6 模块是异步加载，适用于浏览器环境。
// 语法：CommonJS 使用 require 和 exports；ES6 模块使用 import 和 export。
// 执行时机：CommonJS 模块在第一次被 require 时执行，并缓存结果；ES6 模块在导入时执行，并创建模块的引用。
// 环境兼容性：CommonJS 主要在 Node.js 环境中使用，而 ES6 模块在现代浏览器和 Node.js（需要启用 ESM 支持或在 .mjs 文件中使用）中都可以使用。
// 随着 JavaScript 的发展，ES6 模块逐渐成为前端开发的标配，而 CommonJS 主要在 Node.js 后端开发中使用。


// 3.NodeJs是单线程还是多线程?如果是单线程，怎么模拟多线程?如果是多线程，怎么实现互相通行?
// Node.js 是单线程的，但它是基于事件驱动和非阻塞 I/O 模型的，这意味着它可以在单个线程中处理多个并发操作。Node.js 的单线程是指 JavaScript 执行代码是在单个线程中，称为主线程。然而，Node.js 实际上并不是只有一个线程，它还有其他线程用于处理 I/O 操作和计时器等。
// 使用 worker_threads 模块： Node.js 提供了 worker_threads 模块，允许你创建多个工作线程来执行 JavaScript 代码。这些线程可以用于执行计算密集型或 I/O 密集型任务，而不会阻塞主线程。
// 使用 worker_threads 的消息传递： 如前所述，worker_threads 模块允许主线程和工作线程之间通过 postMessage 和 on('message') 事件进行消息传递。
  
  // 1.介绍下 Set、Map、WeakSet和 WeakMap 的区别?
//   Set、Map、WeakSet 和 WeakMap 是 ES6 引入的四种数据结构，它们在JavaScript中用于存储唯一值（Set）、键值对（Map）、弱引用的唯一值（WeakSet）和弱引用的键值对（WeakMap）。下面是它们之间的主要区别：

// Set
// Set 是一个集合数据结构，它存储唯一值，无论是原始值还是对象引用。
// Set 对象中的值可以是任何类型。
// Set 对象中的值是唯一的，不能重复。
// Set 对象是可迭代的，可以使用 for...of 循环遍历。
// Set 对象有 size 属性，可以获取元素的数量。
// Set 对象可以使用 add、delete、has 等方法进行操作。
// Map
// Map 是一个键值对集合，它存储键值对，其中键和值可以是任何类型。
// Map 对象中的键是唯一的，不能重复。
// Map 对象是可迭代的，可以使用 for...of 循环遍历，也可以使用 forEach 方法遍历。
// Map 对象有 size 属性，可以获取键值对的数量。
// Map 对象可以使用 set、get、delete、has 等方法进行操作。
// WeakSet
// WeakSet 是一个集合数据结构，它存储对象弱引用的唯一值。
// WeakSet 对象中的值只能是对象，不能是原始值。
// WeakSet 对象中的值是唯一的，不能重复。
// WeakSet 对象不是可迭代的，不能使用 for...of 循环遍历。
// WeakSet 对象没有 size 属性，无法获取元素的数量。
// WeakSet 对象可以使用 add、delete、has 等方法进行操作。
// WeakSet 对象中的对象如果没有其他引用，则可以被垃圾回收。
// WeakMap
// WeakMap 是一个键值对集合，它存储键为对象弱引用的键值对。
// WeakMap 对象中的键只能是对象，不能是原始值。
// WeakMap 对象中的键是唯一的，不能重复。
// WeakMap 对象不是可迭代的，不能使用 for...of 循环遍历。
// WeakMap 对象没有 size 属性，无法获取键值对的数量。
// WeakMap 对象可以使用 set、get、delete、has 等方法进行操作。
// WeakMap 对象中的对象键如果没有其他引用，则可以被垃圾回收。
// 总结来说，Set 和 Map 是用于存储和操作数据的数据结构，而 WeakSet 和 WeakMap 是用于存储对象弱引用的数据结构，它们的主要区别在于对象的引用强度和迭代能力。WeakSet 和 WeakMap 不适合用于迭代，因为它们的内容可能会随时被垃圾回收。
  
  // 2.介绍下XSS,CSRF 攻击原理以及防范手段
//   XSS（跨站脚本攻击）攻击原理：
// XSS攻击是指攻击者通过在目标网站上注入恶意脚本，使得用户在浏览网页时执行这些脚本。攻击者可以利用这些脚本访问用户的会话cookie、session tokens或其他敏感信息，甚至控制用户的浏览器行为。

// XSS攻击主要有以下几种类型：

// 存储型XSS：恶意脚本被永久存储在目标服务器上，如数据库、消息论坛、访客留言等。
// 反射型XSS：恶意脚本来自当前的HTTP请求，常见于通过URL传递参数的情况。
// DOM型XSS：恶意脚本在客户端运行，通过操作DOM树来达到攻击目的。
// XSS防范手段：
// 输入验证：对用户输入进行验证，只允许合法的输入。例如，在某些字段中只允许特定的字符。
// 输出编码：对输出数据进行编码，确保数据在渲染到页面上时不会被解释为脚本。例如，将 < 和 > 转义为 &lt; 和 &gt;。
// 内容安全策略（CSP）：设置CSP头部，限制资源加载和脚本执行，防止恶意脚本的执行。
// 使用HTTP-only Cookies：设置Cookie为HTTP-only，使得Cookie不能通过JavaScript访问，减少XSS攻击的风险。
// CSRF（跨站请求伪造）攻击原理：
// CSRF攻击是指攻击者利用用户的已认证状态，诱使用户的浏览器向目标网站发送一个恶意请求。由于浏览器已经通过了身份验证，目标网站会认为这个请求是用户自愿发出的，从而导致恶意操作。

// CSRF防范手段：
// 使用CSRF Tokens：在表单中添加一个隐藏的CSRF Token，每次请求时都验证Token的正确性。
// SameSite Cookies：设置Cookie的SameSite属性，限制Cookie在跨站请求时的发送。
// 验证HTTP Referer：检查请求的Referer头部，确保请求来自信任的源。
// 双重认证：对于敏感操作，除了常规认证外，增加额外的认证步骤，如输入验证码或二次确认。
// 总的来说，防范XSS和CSRF攻击需要综合使用多种安全措施，包括输入验证、输出编码、使用安全头部、设置正确的Cookie属性等，以增加攻击的难度。同时，也需要定期更新和修补应用程序的安全漏洞，提高系统的安全性。
  

// 3.说一下发布订阅模式与观察者模式的区别，EventEmitter实现的是哪种模式?
// 发布订阅模式与观察者模式的区别：
// 观察者模式：

// 观察者模式是一种一对多的关系，其中一个对象（称为主题或可观察对象）维护一组依赖于它的对象（称为观察者），当主题的状态发生变化时，它会通知所有观察者对象。
// 观察者直接与主题对象相关联，主题对象通常需要知道观察者的具体信息。
// 观察者模式通常在紧密耦合的系统中使用，观察者和主题之间有直接的引用关系。
// 发布订阅模式：

// 发布订阅模式是一种多对多的关系，发布者发布消息到消息队列或通道，而订阅者从消息队列或通道中订阅消息。
// 发布者和订阅者之间没有直接的联系，它们通过一个中间件（如事件总线或消息队列）进行解耦。
// 发布订阅模式在松散耦合的系统中使用，发布者和订阅者不需要知道对方的详细信息。
// EventEmitter 实现的是哪种模式：
// EventEmitter 是 Node.js 中 events 模块的一个类，它实现了发布订阅模式。EventEmitter 允许对象通过事件名称来注册和触发事件，从而实现对象间的解耦通信。使用 EventEmitter 的对象可以订阅感兴趣的事件，而其他对象可以发布事件，发布者和订阅者之间不需要有直接的引用关系。

// 例如，使用 EventEmitter 的一个简单例子：

// const EventEmitter = require('events');
// const myEmitter = new EventEmitter();

// // 订阅者监听事件
// myEmitter.on('event', (message) => {
//   console.log(message);
// });

// // 发布者触发事件
// myEmitter.emit('event', 'Hello, world!');
// 在这个例子中，myEmitter 是一个发布者，它通过 emit 方法发布了一个名为 event 的事件，而订阅者通过 on 方法订阅了该事件。当事件被触发时，订阅者会收到通知并执行相应的回调函数。


// 4.说一下浏览器的事件循环机制。
// 浏览器的事件循环机制是一个复杂的过程，涉及到多个线程和队列。以下是一个简化版的浏览器事件循环机制的概述：

// 浏览器进程：
// GUI 渲染线程（Renderer Thread）：负责渲染网页内容，包括解析 HTML、CSS 和 JavaScript，以及布局和绘制。
// JavaScript 引擎线程（JavaScript Engine Thread）：负责执行 JavaScript 代码。
// 事件触发线程（Event Triggering Thread）：负责处理 DOM 事件，并调度事件。
// 定时触发器线程（Timer Queue Thread）：负责处理 setTimeout 和 setInterval 回调。
// 网络请求线程（Network Thread）：负责处理网络请求，如 HTTP 请求。
// 异步任务队列（Task Queues）：存储不同类型的异步任务，如宏任务（macrotasks）和微任务（microtasks）。
// 事件循环：
// 事件循环 是一个循环过程，不断地从不同的任务队列中取出任务来执行。
// 宏任务 队列：包括 script（全局代码）、setTimeout、setInterval、I/O、UI rendering 等。
// 微任务 队列：包括 Promise 的 .then() 和 .catch() 回调、MutationObserver 的回调等。
// 事件队列：存储待处理的事件，如鼠标点击、键盘输入等。
// 事件处理流程：
// 解析 HTML：浏览器开始解析 HTML，构建 DOM 树。
// 解析 CSS：浏览器解析 CSS 文件，构建 CSSOM 树。
// 解析 JavaScript：浏览器解析 JavaScript 文件，执行全局代码。
// 构建渲染树：将 DOM 树和 CSSOM 树结合，构建渲染树。
// 布局和绘制：浏览器计算渲染树中每个节点的几何位置和大小，并绘制到屏幕上。
// 执行宏任务：从宏任务队列中取出并执行任务。
// 执行微任务：从微任务队列中取出并执行任务。
// 处理事件：如果有待处理的事件，从事件队列中取出并执行事件处理程序。
// 重复执行：重复执行宏任务、微任务和事件处理，直到没有更多的任务需要执行。
// 任务队列：
// macrotasks：宏任务队列，包括 script（全局代码）、setTimeout、setInterval、I/O、UI rendering 等。
// microtasks：微任务队列，包括 Promise 的 .then() 和 .catch() 回调、MutationObserver 的回调等。
// task queue：存储 requestAnimationFrame、setImmediate（Node.js）等任务。
// 浏览器事件循环机制的核心是确保 JavaScript 代码的执行不会阻塞 GUI 渲染，同时保证异步任务的执行顺序和同步任务的执行顺序是可预测的。

// 5.聊一聊git rebase和git merge的区别
// git rebase 和 git merge 是 Git 版本控制系统中用于合并分支的两种不同的命令。它们的主要区别在于合并的方式和影响。

// git rebase
// 合并方式：git rebase 会将一个或多个分支上的提交应用到另一个分支上，这会重新排列这些提交，使它们看起来像是直接在目标分支上创建的。
// 影响：git rebase 会改变历史记录，因为它会重写提交历史。这可能会影响分支的某些历史提交，如它们的父提交、提交日期等。
// 适用场景：git rebase 适用于想要保持提交历史干净、简洁的场景，尤其是在需要向其他人展示提交历史时。它也常用于解决冲突，因为它可以在冲突发生时暂停，让用户手动解决冲突后再继续。
// git merge
// 合并方式：git merge 会创建一个新的提交，该提交包含两个分支的提交历史，并将两个分支的提交合并在一起。
// 影响：git merge 不会改变历史记录，它只是简单地将两个分支的提交历史合并，并创建一个新的提交。
// 适用场景：git merge 适用于日常开发中的分支合并，尤其是当两个分支上的提交不冲突时。它也常用于解决冲突，但一旦合并完成，原始分支的提交历史就会发生变化。
// 总结
// git rebase 改变历史记录，重写提交历史，适用于需要保持提交历史干净的场景。
// git merge 保持历史记录不变，只是合并两个分支的提交，适用于日常开发中的分支合并。
// 在使用这些命令时，需要根据具体场景和需求来选择合适的方法。在多人协作的项目中，通常推荐使用 git merge 来合并分支，以保持提交历史的清晰和一致性。
  </script>
</body>
</html>